// * proxy 对象包装另外一个对象并拦截诸如读取/写入属性和其他操作。
// * 代理应该在所有地方都完全替代目标对象。目标对象被代理后，任何人都不应该再引用目标对象。否则很容易搞砸。
// * 我们应该在所有地方使用 proxy 而不是 target。代理没有自己的属性或方法。如果提供了捕捉器（trap），它将捕获操作，否则会将其转发给 target 对象。

// 语法：
// let proxy = new Proxy(target, handler)
// target —— 是要包装的对象，可以是任何东西，包括函数。
// handler —— 代理配置：带有“捕捉器（即拦截操作的方法）”的对象。

// *********************************************************************************************

// * 没有捕捉器的代理配置例子，所有操作都直接转发给了 target：
let target = {}
let proxyTarget = new Proxy(target, {}) // 空的 handler 对象

proxyTarget.test = 5

console.log(target.test) // 5
console.log(proxyTarget.test) // 5
for (const key in proxyTarget) console.log(key) // test

// *********************************************************************************************

// * 【get】

// * 捕捉器的例子，拦截数组的读取操作，当数组中没有这一项时，返回 0
let numbers = [0, 1, 2]

numbers = new Proxy(numbers, {
	// 拦截读取操作
	get(target, prop) {
		// console.log('target', target) // [ 0, 1, 2 ]
		// console.log('get', prop) // 1
		if (prop in target) return target[prop]
		else return 0 // 默认返回 0
	},
})

console.log(numbers[1]) // 1
console.log(numbers[3]) // 0（数组中没有这一项）

// ************

// * 拦截属性访问
let dictionary = {
	Hello: 'Hola',
	Bye: 'Adios',
}

dictionary = new Proxy(dictionary, {
	// 拦截读取操作
	get(target, prop) {
		if (prop in target) {
			return target[prop]
		} else {
			return prop // 返回不存在的属性名
		}
	},
})

console.log(dictionary.Bye) // Adios
console.log(dictionary.Test) // Test

// *********************************************************************************************

// * 【set】对于 set 操作，它必须在成功写入时返回 true。如果我们忘记这样做，或返回任何假（falsy）值，则该操作将触发 TypeError。

let numbers2 = []

numbers2 = new Proxy(numbers2, {
	// 拦截写入操作
	set(target, prop, val) {
		console.log({}.toString.call(val))

		if ({}.toString.call(val).includes('Number')) {
			target[prop] = val
			return true
		} else {
			return false
		}
	},
})

numbers2.push(1)
numbers2.push(2)
numbers2.push(3)
// numbers2.push('4') // 报错（类型错误，返回 false）

console.log(numbers2) // [ 1, 2, 3 ]

// *********************************************************************************************

// * ownKeys 捕捉器拦截，Object.keys，for..in 循环。
let user = {
	name: 'John',
	age: 30,
	_password: '***',
}

user = new Proxy(user, {
	// 拦截迭代对象时操作（根据属性的 enumerable 标志）
	ownKeys(target) {
		return Object.keys(target).filter((key) => !key.startsWith('_')) // 迭代时过滤掉开头是 _ 的属性
	},
})

console.log(Object.keys(user)) // [ 'name', 'age' ]
console.log(Object.values(user)) // [ 'John', 30 ]
for (const key in user) console.log(key) // name age

// * 返回不存在的键是无效的
let testObj = {}

testObj = new Proxy(testObj, {
	ownKeys(target) {
		return ['key1', 'key2', 'key3']
	},
})

console.log(Object.keys(testObj)) // []

// *********************************************************************************************

// * 具有 "deleteProperty" 和其他捕捉器的受保护属性
let users = {
	name: 'John',
	_password: '***',
}

// console.log(user._password) // ***

users = new Proxy(users, {
	// 拦截读取属性操作
	get(target, prop) {
		if (prop.startsWith('_')) {
			throw new Error('访问被拒绝')
		}
		let value = target[prop]
		return typeof value === 'function' ? value.bind(target) : value
	},

	// 拦截写入属性操作
	set(target, prop, val) {
		if (prop.startsWith('_')) {
			throw new Error('访问被拒绝')
		} else {
			target[prop] = val
			return true
		}
	},

	// 拦截删除属性操作
	deleteProperty(target, prop) {
		if (prop.startsWith('_')) {
			throw new Error('访问被拒绝')
		} else {
			delete target[prop]
			return true
		}
	},

	// 拦截遍历属性操作
	ownKeys(target) {
		return Object.keys(target).filter((key) => !key.startsWith('_'))
	},
})

try {
	// console.log(users._password) // 读取失败
	// users._password = '12345' // 写入失败
	// delete users._password // 删除失败
} catch (error) {
	console.log(error)
}

// 遍历没有_开头的属性
for (const key in users) console.log(key) // name

// *********************************************************************************************

// * has 捕捉器拦截 in 调用

let range = {
	start: 1,
	end: 10,
}

range = new Proxy(range, {
	has(target, prop) {
		return prop >= target.start && prop <= target.end
	},
})

console.log(5 in range) // true
console.log(15 in range) // false

// *********************************************************************************************

// * apply 捕捉器（函数也是一个对象）

// * 实现一个包装器函数（延时装饰器）
function delay(f, ms) {
	return new Proxy(f, {
		apply(target, thisArg, args) {
			setTimeout(() => target.apply(thisArg, args), ms)
		},
	})
}

function sayHi(name) {
	console.log(`Hello, ${name}`)
}

sayHi = delay(sayHi, 1000)

console.log(sayHi.name, sayHi.length) // sayHi 1
sayHi('John') // Hello, John（1s后打印）

// *********************************************************************************************
// *********************************************************************************************
// *********************************************************************************************

// * Reflect 是一个内建对象，可简化 Proxy 的创建。
// * Reflect 可以在 Proxy 中调用内部的 [[Get]] 和 [[Set]] 等方法。

// * Reflect 调用：
// * Reflect.get(obj, prop)
// * Reflect.set(obj, prop, value)
// * Reflect.deleteProperty(obj, prop)
// * Reflect.construct(F, value)

// 例子：
let obj = {}
Reflect.set(obj, 'name', 'John')
console.log(obj.name) // John

// * 对于每个可被 Proxy 捕获的内部方法，在 Reflect 中都有一个对应的方法，其名称和参数与 Proxy 捕捉器相同。

let testObjs = {
	name: 'Ben',
}

testObjs = new Proxy(testObjs, {
	get(target, prop, receiver) {
		console.log(`GET ${prop}`)
		return Reflect.get(target, prop, receiver)
	},
	set(target, prop, val, receiver) {
		console.log(`SET ${prop}`)
		return Reflect.set(target, prop, val, receiver)
	},
})

let name = testObjs.name
testObjs.name = 'Pete'

// *********************************************************************************************

// * 代理一个 getter

let user2 = {
	_name: 'Guest',

	get name() {
		return this._name
	},
}

let userProxy = new Proxy(user2, {
	// get(target, prop, receiver) {
	// 	return target[prop] // target 是 user2
	// },

	get(target, prop, receiver) {
		return Reflect.get(target, prop, receiver) // receiver = admin
		// return Reflect.get(...arguments) // Reflect.get 简写形式
	},
})

let admin = {
	__proto__: userProxy,
	_name: 'Admin',
}

// * admin 的原型指向 userProxy 这个代理对象，而 userProxy 的代理对象的源对象是 user2，所以 userProxy.name 会返回 'Guest'。
// console.log(admin.name) // Guest

// * Reflect.get(target, prop, receiver) 指定了 this = admin。receiver 保留了正确的 this 引用，Reflect.get 传递给 getter 的。
console.log(admin.name) // Admin

// *********************************************************************************************

// * 可撤销 Proxy

// * 一个 可撤销 的代理是可以被禁用的代理。
// * 假设我们有一个资源，并且想随时关闭对该资源的访问。
// * 语法为：let {proxy, revoke} = Proxy.revocable(target, handler)。

let object = {
	data: 'Valuable data',
}

let { proxy, revoke } = Proxy.revocable(object, {})

console.log(proxy.data) // Valuable data

revoke() // 取消代理

// proxy 代理对象不再工作（revoked）
console.log(proxy.data) // Error
